﻿using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Hermite节点类，用于存储曲线上的点和对应的时间
/// </summary>
public class HerimiteNode
{
    /// <summary>
    /// 曲线上的点
    /// </summary>
    public Vector3 point;
    /// <summary>
    /// 点对应的时间
    /// </summary>
    public float time;

    /// <summary>
    /// 构造函数，初始化点和时间
    /// </summary>
    /// <param name="v">点的位置</param>
    /// <param name="t">点的时间</param>
    public HerimiteNode(Vector3 v, float t)
    {
        point = v;
        time = t;
    }

    /// <summary>
    /// 复制构造函数，创建一个与给定节点相同的节点
    /// </summary>
    /// <param name="n">要复制的节点</param>
    public HerimiteNode(HerimiteNode n)
    {
        point = n.point;
        time = n.time;
    }
}

/// <summary>
/// 线条工具类，提供各种线条计算方法
/// </summary>
public static class LineTools
{
    private const float GravityFactor = 8f; // 定义重力相关的常量
    private const float SmoothFactorDefault = 2f; // 默认平滑因子

    /// <summary>
    /// 计算3点贝塞尔曲线上的点
    /// </summary>
    /// <param name="start">起点</param>
    /// <param name="center">中间控制点</param>
    /// <param name="end">终点</param>
    /// <param name="t">参数t，范围[0,1]</param>
    /// <returns>曲线上的点</returns>
    public static Vector3 GetBezierPos(Vector3 start, Vector3 center, Vector3 end, float t)
    {
        Vector3 p0p1 = (1 - t) * start + t * center;
        Vector3 p1p2 = (1 - t) * center + t * end;
        Vector3 result = (1 - t) * p0p1 + t * p1p2;
        return result;
    }

    /// <summary>
    /// 计算4点贝塞尔曲线上的点
    /// </summary>
    /// <param name="t">参数t，范围[0,1]</param>
    /// <param name="p0">起点</param>
    /// <param name="p1">第一控制点</param>
    /// <param name="p2">第二控制点</param>
    /// <param name="p3">终点</param>
    /// <returns>曲线上的点</returns>
    public static Vector3 GetBezierPos4(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
    {
        float u = 1 - t;
        float tt = t * t;
        float uu = u * u;
        float uuu = uu * u;
        float ttt = tt * t;

        Vector3 p = uuu * p0;
        p += 3 * uu * t * p1;
        p += 3 * u * tt * p2;
        p += ttt * p3;

        return p;
    }

    /// <summary>
    /// 计算Hermite曲线上的点
    /// </summary>
    /// <param name="t">参数t，表示时间</param>
    /// <param name="points">节点列表</param>
    /// <param name="smoothFactor">平滑因子</param>
    /// <returns>曲线上的点</returns>
    public static Vector3 GetHerimite(float t, List<HerimiteNode> points, int smoothFactor = 2)
    {
        if (points == null || points.Count < 3)
        {
            throw new ArgumentException("节点列表必须包含至少3个节点");
        }

        int k;
        if (t >= points[points.Count - 2].time)
        {
            return points[points.Count - 2].point;
        }

        for (k = 1; k < points.Count - 2; k++)
        {
            if (points[k].time > t)
                break;
        }

        k = k - 1;
        float param = (t - points[k].time) / (points[k + 1].time - points[k].time);
        return GetHermitNode(k, param, points, smoothFactor);
    }

    /// <summary>
    /// 闭合Hermite曲线，通过复制首尾节点
    /// </summary>
    /// <param name="points">节点列表</param>
    public static void CloseHerimite(List<HerimiteNode> points)
    {
        if (points == null || points.Count < 2)
        {
            throw new ArgumentException("节点列表必须包含至少2个节点");
        }

        points.Insert(0, new HerimiteNode(points[0]));
        points.Add(new HerimiteNode(points[points.Count - 1]));
    }

    /// <summary>
    /// 计算Hermite曲线上的点
    /// </summary>
    /// <param name="index">节点索引</param>
    /// <param name="t">参数t，范围[0,1]</param>
    /// <param name="points">节点列表</param>
    /// <param name="smoothFactor">平滑因子</param>
    /// <returns>曲线上的点</returns>
    public static Vector3 GetHermitNode(int index, float t, List<HerimiteNode> points, float smoothFactor)
    {
        if (index < 1 || index >= points.Count - 2)
        {
            throw new ArgumentOutOfRangeException(nameof(index), "索引超出有效范围");
        }

        Vector3 P0 = points[index - 1].point;
        Vector3 P1 = points[index].point;
        Vector3 P2 = points[index + 1].point;
        Vector3 P3 = points[index + 2].point;

        // 调和函数
        float h1 = 2 * t * t * t - 3 * t * t + 1;
        float h2 = -2 * t * t * t + 3 * t * t;
        float h3 = t * t * t - 2 * t * t + t;
        float h4 = t * t * t - t * t;

        return h1 * P1 + h2 * P2 + h3 * (P2 - P0) / smoothFactor + h4 * (P3 - P1) / smoothFactor;
    }

    /// <summary>
    /// 根据距离、重力和速度计算抛物线的起始矢量和耗时
    /// </summary>
    /// <param name="start">起点</param>
    /// <param name="end">终点</param>
    /// <param name="fGavity">重力加速度</param>
    /// <param name="fMoveSpeed">水平速度</param>
    /// <param name="t1">耗时</param>
    /// <returns>起始矢量</returns>
    public static Vector3 GetMoveDir(Vector3 start, Vector3 end, float fGavity, float fMoveSpeed, out float t1)
    {
        if (fGavity <= 0 || fMoveSpeed <= 0)
        {
            throw new ArgumentException("重力加速度和水平速度必须为正数");
        }

        var dis = end - start;
        dis.y = 0;
        float s1 = dis.magnitude; // 得到水平距离
        t1 = s1 / fMoveSpeed;     // 得到水平移动的时间

        float v = (2 * dis.y + fGavity * t1 * t1) / (2 * t1);
        return dis / t1 + Vector3.up * v;
    }

    /// <summary>
    /// 计算抛物线上的点
    /// </summary>
    /// <param name="startPoint">起点</param>
    /// <param name="endPoint">终点</param>
    /// <param name="speed">速度</param>
    /// <param name="gravity">重力加速度</param>
    /// <param name="time">参数t，范围[0,1]</param>
    /// <returns>抛物线上的点</returns>
    public static Vector3 GetParabola(Vector3 startPoint, Vector3 endPoint, float speed, float gravity, float time)
    {
        if (speed <= 0 || gravity <= 0)
        {
            throw new ArgumentException("速度和重力加速度必须为正数");
        }

        Vector3 direction = (endPoint - startPoint).normalized;
        float distance = Vector3.Distance(startPoint, endPoint);
        float duration = distance / speed;
        float maxHeight = gravity * duration * duration / GravityFactor;

        Func<float, float> f = x => -4 * maxHeight * x * x + 4 * maxHeight * x;
        var mid = Vector3.Lerp(startPoint, endPoint, time);
        return new Vector3(mid.x, f(time) + Mathf.Lerp(startPoint.y, endPoint.y, time), mid.z);
    }

    /// <summary>
    /// 获取弧线上的点
    /// </summary>
    /// <param name="start">弧线的第一个点</param>
    /// <param name="end">弧线的第二个点</param>
    /// <param name="origin">所在圆的圆心</param>
    /// <param name="step">分成多少段</param>
    /// <returns>弧线上的点列表</returns>
    public static List<Vector3> GetLineArc(Vector3 start, Vector3 end, Vector3 origin, int step)
    {
        if (step < 2)
        {
            throw new ArgumentException("步数必须大于等于2");
        }

        List<Vector3> arcPoints = new List<Vector3>();
        Quaternion q = Quaternion.FromToRotation(start - origin, end - origin);

        Vector3 dir = start - origin;
        for (int i = 0; i < step; i++)
        {
            float prog = (float)i / (step - 1);
            arcPoints.Add(origin + Quaternion.Lerp(Quaternion.identity, q, prog) * dir);
        }

        return arcPoints;
    }

    /// <summary>
    /// 获取上抛的轨迹在指定时间的高度
    /// </summary>
    /// <param name="startY">起始高度</param>
    /// <param name="endY">结束高度</param>
    /// <param name="maxHeight">最大高度</param>
    /// <param name="fG">重力加速度</param>
    /// <param name="t">时间参数(秒)</param>
    /// <param name="dir">运动方向:1=上升,-1=下降,0=落地</param>
    /// <returns>指定时间的高度</returns>
    public static float GetHeightThrow(float startY, float endY, float maxHeight, float fG, float t, out int dir)
    {
        dir = 0;
        if (t < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(t), "时间参数必须大于等于0");
        }

        // 计算上升阶段时间
        float riseTime = Mathf.Sqrt(2 * (maxHeight - startY) / fG);
        // 计算总时间(上升+下降)
        float totalTime = riseTime + Mathf.Sqrt(2 * (maxHeight - endY) / fG);

        float height;
        if (t <= riseTime)
        {
            // 上升阶段: h = startY + v0*t - 0.5*fG*t^2
            float v0 = Mathf.Sqrt(2 * fG * (maxHeight - startY));
            height = startY + v0 * t - 0.5f * fG * t * t;
            dir = 1;
        }
        else if (t <= totalTime)
        {
            // 下降阶段: h = maxHeight - 0.5*fG*(t - riseTime)^2
            height = maxHeight - 0.5f * fG * (t - riseTime) * (t - riseTime);
            dir = -1;
        }
        else
        {
            // 已落地
            height = endY;
            dir = 0;
            return height;
        }

        // 检查是否已经落地(高度<=endY)
        if (height <= endY)
        {
            height = endY;
            dir = 0;
        }

        return height;
    }

}
